---
title: Multi-column Layouts
---

import { PuckPreview } from "@/docs/components/Preview";
import { Puck } from "@/puck";
import { Callout } from "nextra/components";

# Multi-column Layouts

Puck supports nested and multi-column layouts across any CSS layout using the [`slot` field](/docs/api-reference/fields/slot).

<Callout type="info">Slots replace the [`<DropZone>` component](/docs/api-reference/components/drop-zone) component, which will soon be deprecated and removed. For migration notes, see [these docs](/docs/guides/migrations/dropzones-to-slots).</Callout>

## Nested components

Add the [`slot`](/docs/api-reference/fields/slot) field to your component to create a zone that you can drop components into.

```tsx {5-7,10} showLineNumbers copy
const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => {
        return <Content />;
      },
    },
    Card: {
      render: () => <div>Hello, world</div>,
    },
  },
};
```

<PuckPreview
  label="Nested components example"
  config={{
    components: {
      Example: {
        fields: {
          content: {
            type: "slot",
          },
        },
        render: ({ content: Content }) => {
          return (
            <div style={{ padding: 32 }}>
              <Content />
            </div>
          );
        },
      },
      Card: {
        render: () => {
          return (
            <div
              style={{
                background: "white",
                border: "1px solid black",
                borderRadius: 4,
                padding: 16,
              }}
            >
              Hello, world
            </div>
          );
        },
      },
    },
  }}
  data={{
    content: [
      {
        type: "Example",
        props: {
          id: "Example-1",
          content: [{ type: "Card", props: { id: "Example-2" } }],
        },
      },
    ],
    root: { props: {} },
  }}
>
  <Puck.Preview />
</PuckPreview>

## Fixed layouts

Combine multiple DropZones to achieve fixed layouts. By default, components inside a DropZone are arranged along the vertical (`block`) axis.

```tsx {5-10,17,18} showLineNumbers copy
const config = {
  components: {
    Example: {
      fields: {
        leftColumn: {
          type: "slot",
        },
        rightColumn: {
          type: "slot",
        },
      },
      render: ({ leftColumn: LeftColumn, rightColumn: RightColumn }) => {
        return (
          <div
            style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}
          >
            <LeftColumn />
            <RightColumn />
          </div>
        );
      },
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
```

<PuckPreview
  label="Fixed layout example"
  config={{
    root: {
      render: ({ puck: { renderDropZone: DropZone } }) => (
        <DropZone zone="default-zone" disallow={["Card"]} />
      ),
    },
    components: {
      Example: {
        fields: {
          leftColumn: {
            type: "slot",
          },
          rightColumn: {
            type: "slot",
          },
        },
        render: ({ leftColumn: LeftColumn, rightColumn: RightColumn }) => {
          return (
            <div
              style={{
                display: "grid",
                gridTemplateColumns: "1fr 1fr",
                gap: 16,
              }}
            >
              <LeftColumn />
              <RightColumn />
            </div>
          );
        },
      },
      Card: {
        render: ({ content }) => {
          return (
            <div
              style={{
                background: "white",
                border: "1px solid black",
                borderRadius: 4,
                padding: 16,
              }}
            >
              {content}
            </div>
          );
        },
      },
    },
  }}
  data={{
    content: [
      {
        type: "Example",
        props: {
          id: "Example-1",
          leftColumn: [
            {
              type: "Card",
              props: { id: "Example-2", content: "1" },
            },
          ],
          rightColumn: [
            {
              type: "Card",
              props: { id: "Example-3", content: "2" },
            },
          ],
        },
      },
    ],
    root: { props: {} },
  }}
>
  <Puck.Preview />
</PuckPreview>

## Fluid layouts

Apply the [CSS display](https://developer.mozilla.org/en-US/docs/Web/CSS/display) property to a slot via the [`style`](/docs/api-reference/components/drop-zone#style) or [`className`](/docs/api-reference/components/drop-zone#className) props to arrange your components in different layouts. Puck supports drag-and-drop for all `display` values, including `grid` and `flex`.

```tsx {12-15} showLineNumbers copy
const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => (
        <Content
          style={{
            // Use CSS grid in this slot
            display: "grid",
            gridTemplateColumns: "2fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
```

<PuckPreview
  label="Fluid layout using CSS grid"
  config={{
    root: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => (
        <Content
          style={{
            display: "grid",
            gridTemplateColumns: "2fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    components: {
      Card: {
        render: ({ content }) => {
          return (
            <div
              style={{
                background: "white",
                border: "1px solid black",
                borderRadius: 4,
                padding: 16,
              }}
            >
              {content}
            </div>
          );
        },
      },
    },
  }}
  data={{
    content: [],
    root: {
      props: {
        content: [
          {
            type: "Card",
            props: { id: "Example-2", content: "1" },
          },
          {
            type: "Card",
            props: { id: "Example-3", content: "2" },
          },
        ],
      },
    },
  }}
>
  <Puck.Preview />
</PuckPreview>

## Removing the wrapper

By default, Puck will wrap your components in a `div` element. For some layouts, you may need to eliminate the wrapping element and treat the child component as a direct descendant of its' parent slot.

For example, this is required if you wish to use CSS rules like `flex-grow`, `grid-column`, or `grid-row`.

Use the [`inline`](/docs/api-reference/configuration/component-config#inline) component parameter to remove the wrapping element. **When using this API, you must also specify which element is draggable by passing the [`puck.dragRef` prop](/docs/api-reference/configuration/component-config#puckdragref) to your element's `ref` prop.**

```tsx {13-14,21,24-28} showLineNumbers copy
const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      render: ({ content: Content }) => (
        <Content
          style={{
            display: "grid",
            gridTemplateColumns: "1fr 1fr 1fr 1fr",
            gridTemplateRows: "1fr 1fr 1fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    Card: {
      inline: true, // Enable inline mode, removing the Puck wrapper
      render: ({ text, spanCol, spanRow, puck }) => (
        <div
          ref={puck.dragRef} // Let Puck know this element is draggable
          style={{
            gridColumn: `span ${spanCol}`,
            gridRow: `span ${spanRow}`,
          }}
        >
          {text}
        </div>
      ),
    },
  },
};
```

<PuckPreview
  label="Advanced grid example"
  config={{
    root: {
      fields: {
        Content: {
          type: "slot",
        },
      },
      render: ({ Content }) => (
        <Content
          zone="default-zone"
          style={{
            display: "grid",
            gridTemplateColumns: "1fr 1fr 1fr 1fr",
            gridTemplateRows: "1fr 1fr 1fr",
            gap: 16,
          }}
        />
      ),
    },
    components: {
      Card: {
        inline: true,
        render: ({ content, spanCol, spanRow, puck }) => {
          return (
            <div
              style={{
                background: "white",
                border: "1px solid black",
                borderRadius: 4,
                padding: 16,
                gridColumn: `span ${spanCol}`,
                gridRow: `span ${spanRow}`,
              }}
              // Let Puck know this element is draggable
              ref={puck.dragRef}
            >
              {content}
            </div>
          );
        },
      },
    },

}}
data={{
    content: [],
    root: {
      props: {
        Content: [
          {
            type: "Card",
            props: { id: "Example-1", content: "1", spanCol: 2, spanRow: 2 },
          },
          {
            type: "Card",
            props: { id: "Example-2", content: "2", spanCol: 1, spanRow: 1 },
          },
          {
            type: "Card",
            props: { id: "Example-3", content: "3", spanCol: 1, spanRow: 1 },
          },
          {
            type: "Card",
            props: { id: "Example-4", content: "4", spanCol: 2, spanRow: 1 },
          },
          {
            type: "Card",
            props: { id: "Example-5", content: "5", spanCol: 1, spanRow: 1 },
          },
        ],
      },
    },
  }}
/>

## Restricting components

Use the [`allow`](/docs/api-reference/fields/slot#allow) and [`disallow`](/docs/api-reference/fields/slot#disallow) parameters to restrict which components can be dragged into a slot.

```tsx {6} showLineNumbers copy
const config = {
  components: {
    fields: {
      content: {
        type: "slot",
        allow: ["Card"],
      },
    },
    Example: {
      render: ({ content: Content }) => {
        return <Content />;
      },
    },
  },
};
```

Combine this with [categories](/docs/integrating-puck/categories) to restrict behavior based on your existing groups.

```tsx {2-6,12} showLineNumbers copy
const config = {
  categories: {
    typography: {
      components: ["Card"],
    },
  },
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
          allow: categories.typography.components,
        },
      },
      render: ({ content: Content }) => {
        return <Content />;
      },
    },
  },
};
```

Alternatively, you can provide `allow` and `disallow` [to your render function](/docs/api-reference/fields/slot#allow-1).

## Setting default props

Use slots with [`defaultProps`](/docs/api-reference/configuration/component-config#defaultprops) to pre-populate it when the component is inserted with an array of [`ComponentData`](/docs/api-reference/data-model/component-data).

```tsx {9-18} showLineNumbers copy
const config = {
  components: {
    Example: {
      fields: {
        content: {
          type: "slot",
        },
      },
      defaultProps: {
        content: [
          {
            type: "Card",
            props: {
              text: "Pre-populated",
            },
          },
        ],
      },
      render: ({ content: Content }) => <Content />,
    },
    Card: {
      render: ({ text }) => <div>{text}</div>,
    },
  },
};
```

<PuckPreview
  label="Fluid layout using CSS grid"
  config={{
    root: {
      fields: {
        content: {
          type: "slot",
        },
      },
      defaultProps: {
        content: [
          {
            type: "Card",
            props: {
              text: "Pre-populated",
            },
          },
        ],
      },
      render: ({ content }) =>
        content({
          style: {
            display: "grid",
            gridTemplateColumns: "2fr 1fr",
            gap: 16,
          },
        }),
    },
    components: {
      Card: {
        render: ({ content }) => {
          return (
            <div
              style={{
                background: "white",
                border: "1px solid black",
                borderRadius: 4,
                padding: 16,
              }}
            >
              {content}
            </div>
          );
        },
      },
    },
  }}
  data={{
    content: [],
    root: {
      props: {
        content: [
          {
            type: "Card",
            props: { id: "Example-2", content: "Pre-populated" },
          },
        ],
      },
    },
  }}
>
  <Puck.Preview />
</PuckPreview>

## Further reading

- [The `slot` field API](/docs/api-reference/fields/slot)
- [The `inline` component config](/docs/api-reference/configuration/component-config#inline)
